package com.api.rc.lobby;

import com.api.rc.dao.RoomMapper;
import com.api.rc.enums.RoomStatus;
import com.api.rc.lobby.domain.LobbyPlayer;
import com.api.rc.lobby.domain.LobbyRoom;
import com.api.rc.lobby.message.LobbyEvent;
import com.api.rc.lobby.message.MessageGenerator;
import com.api.rc.model.Host;
import com.api.rc.model.Room;
import com.api.rc.service.HostService;
import com.api.user.enums.GameEnum;
import com.api.user.service.GameService;
import com.api.user.vo.UserInfoVo;
import com.google.common.base.Preconditions;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.socket.WebSocketSession;
import tk.mybatis.mapper.entity.Condition;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author RenQiang
 * @date 2021/7/13
 */
@Slf4j
@Component
public class LobbyManagerImpl implements LobbyManager {
    private static final int ROOM_COUNT = 128;
    private final ConcurrentHashMap<Long, LobbyRoom> roomMap = new ConcurrentHashMap<>(ROOM_COUNT);
    /**
     * 用户所处房间
     */
    private final ConcurrentHashMap<Long, Long> userRoomMap = new ConcurrentHashMap<>(ROOM_COUNT * 40);

    private final RoomMapper roomMapper;
    private final GameService gameService;
    private final HostService hostService;

    public LobbyManagerImpl(RoomMapper roomMapper, GameService gameService, HostService hostService) {
        this.roomMapper = roomMapper;
        this.gameService = gameService;
        this.hostService = hostService;
    }

    @PostConstruct
    public void initRooms() {
        Condition condition = new Condition(Room.class);
        condition.createCriteria()
                .andEqualTo("status", RoomStatus.WaitingToStart);
        List<Room> rooms = roomMapper.selectByCondition(condition);
        if (CollectionUtils.isEmpty(rooms)) {
            return;
        }
        String hostIds = rooms.stream()
                .map(Room::getHostId)
                .map(Object::toString)
                .collect(Collectors.joining(","));
        List<Host> hosts = hostService.findByIds(hostIds);
        Map<Long, Long> hostIdUserIdMap = hosts.stream()
                .map(host -> Pair.of(host.getId(), host.getUserId()))
                .collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
        for (Room room : rooms) {
            GameEnum gameEnum = gameService.getGameEnumById(room.getGameId());
            this.createRoom(room.getId(), hostIdUserIdMap.get(room.getHostId()), gameEnum.calcTeamCount(room.getParticipants()), gameEnum.calcPlayerPerTeam(room.getParticipants()));
        }
    }

    @Override
    public void createRoom(Long roomId, long hostUserId, int teamCount, int membersPerTeam) {
        LobbyRoom lobbyRoom = new LobbyRoom(roomId, hostUserId, teamCount, membersPerTeam);
        this.roomMap.put(roomId, lobbyRoom);
        log.info("创建大厅房间 ==> roomId: {}, totalRooms: {}", roomId, roomMap.size());
    }

    @Override
    public LobbyRoom getLobbyRoom(Long roomId) {
        return roomMap.get(roomId);
    }

    @Override
    public void destroyRoom(Long roomId, String remark) {
        LobbyRoom lobbyRoom = roomMap.get(roomId);
        if (lobbyRoom != null) {
            for (LobbyPlayer player : lobbyRoom.getAllPlayers()) {
                if (player.getSession().isOpen()) {
                    try {
                        player.getSession().sendMessage(MessageGenerator.genSystemMessage(LobbyEvent.SERVER_ROOM_DESTROYED, remark));
                        player.getSession().close();
                    } catch (IOException e) {
                        log.error("发送WebSocket消息失败", e);
                    }
                }
            }
            roomMap.remove(roomId);
            log.info("销毁大厅房间 ==> roomId: {}, totalRooms: {}", roomId, roomMap.size());
        }
    }

    @Override
    public void joinRoom(UserInfoVo userInfo, Long roomId, WebSocketSession session) {
        LobbyRoom lobbyRoom = roomMap.get(roomId);
        Preconditions.checkArgument(lobbyRoom != null, "房间不存在");
        //主持人进入房间
        if (userInfo.getId() == lobbyRoom.getHostUserId()) {
            lobbyRoom.hostJoinIn(new LobbyPlayer(userInfo, session));
        }
        //玩家进入房间
        else {
            lobbyRoom.addFreeMan(new LobbyPlayer(userInfo, session));
        }
        userRoomMap.put(userInfo.getId(), roomId);

        log.info("用户加入房间 ==> userId: {}, roomSummary: {}", userInfo.getId(), lobbyRoom.summary());
        //给当前用户推送房间详情
        try {
            session.sendMessage(MessageGenerator.genRoomDetailMessage(lobbyRoom));
        } catch (IOException e) {
            log.error("发送WebSocket消息失败", e);
        }
    }

    @Override
    public void exitRoom(Long userId) {
        Long roomId = userRoomMap.get(userId);
        if (roomId != null) {
            LobbyRoom lobbyRoom = roomMap.get(roomId);
            lobbyRoom.removePlayer(userId);
            userRoomMap.remove(userId);
            log.info("用户退出房间 ==> userId: {}, roomSummary: {}", userId, lobbyRoom.summary());
        }
    }

    @Override
    public void joinTeam(Long userId, int teamNo) {
        Long roomId = userRoomMap.get(userId);
        if (roomId != null) {
            LobbyRoom lobbyRoom = roomMap.get(roomId);
            if (lobbyRoom.getHostUserId() == userId) {
                throw new IllegalArgumentException("主持人不能加入队伍");
            }
            lobbyRoom.changeUserTeam(userId, teamNo);
            log.info("用户加入队伍 ==> userId: {}, roomSummary: {}", userId, lobbyRoom.summary());
        }
    }

    @Override
    public void readyToGame(Long userId) {
        Long roomId = userRoomMap.get(userId);
        if (roomId != null) {
            LobbyRoom lobbyRoom = roomMap.get(roomId);
            if (lobbyRoom.getHostUserId() == userId) {
                throw new IllegalArgumentException("主持人不能准备游戏");
            }
            lobbyRoom.playerReady(userId);
            log.info("玩家准备游戏 ==> userId: {}, roomSummary: {}", userId, lobbyRoom.summary());
        }
    }
}
